/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is GWT RTTI library.
 *
 * The Initial Developer of the Original Code is
 * Jan "SHadoW" Rames <ramejan@gmail.com>.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 */
package com.promis.rtti.generator;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracleException;
import com.promis.generators.BaseGenerator;
import com.promis.generators.BaseGeneratorClient;
import com.promis.rtti.annotations.GenerateRtti;
import com.promis.rtti.annotations.RttiPackage;
import com.promis.rtti.client.RttiObject;

/**
 * Generator that generates RTTI for specified classes
 * using lazy creation of RTTI reflection classes which
 * are generated by {@link RttiClassGenerator}. Son unless
 * RTTI is requested no extra memory is used. The RTTI itself
 * is is then cached for further usage. 
 * @author SHadoW
 *
 */
public class RttiGenerator extends BaseGenerator
{
	@Override
	protected BaseGeneratorClient getClient()
	{
		return new Client();
	}
	static private boolean isSubtype(JClassType type, Class<?> classType)
	{
		final String name = classType.getCanonicalName();
		
		while (type != null)
		{
			if (name.equals(type.getQualifiedSourceName())) return true;
			type = type.getSuperclass();
		}
		
		return false;
	}

	static class Client extends BaseGeneratorClient
	{

		private JType findType(Class<?> clazz)
		{
			if (clazz.isPrimitive()) try
			{
				return oracle.parse(clazz.getName());
			}
			catch (TypeOracleException e)
			{
				throw new RuntimeException(e);
			}
			else return oracle.findType(clazz.getName());
		}
		
		protected void doAddTypes(List<JType> types, JClassType[] from, TreeLogger l, 
				boolean isNested)
		{
			for (JClassType type : from)
			{
				if (type.isPublic() && (isNested || type.isAnnotationPresent(GenerateRtti.class) || 
						isSubtype(type, RttiObject.class)))
				{
					l.log(Type.INFO, type.getQualifiedBinaryName());
					types.add(type);
					
					doAddTypes(types, type.getNestedTypes(), l, true);
				}
			}
		}
		
		@Override
		protected void doGenerate()
		{
			List<JType> types = new ArrayList<JType>();
			TreeLogger logger = this.logger.branch(Type.INFO, "Packages");
			
			logger.branch(Type.INFO, "java.lang").log(Type.INFO, "Adding primitives");
			
			types.add(findType(boolean.class));
			types.add(findType(int.class));
			types.add(findType(String.class));
			types.add(findType(char.class));
			types.add(findType(byte.class));
			types.add(findType(short.class));
			types.add(findType(long.class));
			types.add(findType(float.class));
			types.add(findType(double.class));
			types.add(findType(void.class));
			
			/*types.add(findType(boolean.class));
			types.add(findType(Boolean.class));
			types.add(findType(Integer.class));
			types.add(findType(String.class));
			types.add(findType(Character.class));
			types.add(findType(Byte.class));
			types.add(findType(Short.class));
			types.add(findType(Long.class));
			types.add(findType(Float.class));
			types.add(findType(Double.class));*/
			
			for (JPackage pac : oracle.getPackages())
			{
				if (pac.isAnnotationPresent(RttiPackage.class))
				{
					TreeLogger l = logger.branch(Type.INFO, pac.getName());
					doAddTypes(types, pac.getTypes(), l, false);
				}
			}
		
			int i = 0;
			for (JType type : types)
			{
				//Primitives have a little different handling
				JPrimitiveType primitive = type.isPrimitive();
				if (primitive != null) type = oracle.findType(primitive.getQualifiedBoxedSourceName());
				write("static abstract class T%d_%s extends RttiClassImpl<%s> {}", 
						i, type.getSimpleSourceName(), type.getQualifiedSourceName());
				i++;
			}
			newline();
			writeBlockIntro("public RttiClass find(final String className)");
			i = 0;
			for (JType type : types)
			{
				JPrimitiveType primitive = type.isPrimitive();
				if (primitive != null) type = oracle.findType(primitive.getQualifiedBoxedSourceName());
				
				write("if (\"%s\".equals(className))", 
						type.getQualifiedBinaryName());
				indent();
				writeReturn("GWT.create(T%d_%s.class)", i, type.getSimpleSourceName());
				outdent();
				
				if (primitive != null)
				{
					//Use event non-class representation
					write("if (\"%s\".equals(className))", 
							primitive.getQualifiedSourceName());
					indent();
					writeReturn("GWT.create(T%d_%s.class)", i, type.getSimpleSourceName());
					outdent();
				}
				i++;
			}
			writeReturn(NULL);
			writeBlockOutro();			
		}
		
	}
}
